using AnimCollections;
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Playables;

namespace DA.AnimGraph {
    public unsafe struct AnimGraph : IDisposable {

        public GraphArrayInfo graphArrayInfo;
        public GraphIdentification graphIdentification;

        public AUID uuid { get => graphIdentification.uuid; set => graphIdentification.uuid = value; }
        public AUID name { get => graphIdentification.name; set => graphIdentification.name = value; }
        public Playable rootPlayable => rootNode->playable;
        public bool isValid => isInitialized && !isDisposed && rootNode != null;

        private bool isInitialized;
        private bool isDisposed;
        public NodeLink* rootNode { get; private set; }

        private GraphBuffer graphBuffer;

        //private BuildContext buildContext;
        private GraphContext graphContext;
        internal NativeHashMap<AUID, Slot> slots;

        /// <summary>
        /// 代替构造函数
        /// GraphBuffer 产生后先调用此方法在开头设置AnimGraph，然后构建节点（节点接着排布）
        /// </summary>
        public static AnimGraph* New(ref GraphBuffer graphBuffer) {
            var graph = graphBuffer.Malloc<AnimGraph>();
            *graph = new();
            graph->graphBuffer = graphBuffer;
            return graph;
        }

        /// <summary>
        /// 构建节点完成后设置给AnimGraph
        /// </summary>
        public void Init(in NodeLink* rootNode) {
            Init(rootNode, new GraphContext());
        }

        // todo:此方法要改成私有
        public void Init(in NodeLink* rootNode, in GraphContext graphContext) {
            this.rootNode = rootNode;
            this.graphContext = graphContext;

            isInitialized = true;
            isDisposed = false;
        }

        public void SetBlackboardBaseAddress<T>(in T memorySlice) where T : unmanaged {
            if (!graphContext.blackboard.IsValid) {
                graphContext.blackboard = new Blackboard(4);
            }
            graphContext.blackboard.SetBaseAddress(memorySlice);
        }

        public void BindBlackboardField<T>(AUID uuid, in T field) where T : unmanaged {
            if (!graphContext.blackboard.IsValid) {
                Debug.LogError("Call SetBlackboardBaseAddress Frist!!");
            }
            graphContext.blackboard.BindField(uuid, field);
        }

        /// <summary>
        /// 清理当前的Blackboard，再复制传入的
        /// </summary>
        public void SetBlackboard(in Blackboard blackboard) {
            if (graphContext.blackboard.IsValid) {
                graphContext.blackboard.Dispose();
            }
            graphContext.blackboard = new Blackboard(blackboard);
        }

        internal void InitNode() {
            slots = new(2, Allocator.Persistent);
            InitContext initContext = new(slots);
            rootNode->Init(initContext);
            slots = initContext.slots;

            isInitialized = true;
        }

        internal void BuildPlayableGraph(in PlayableGraph playableGraph, in BuildContext buildContext) {
            rootNode->BuildPlayableGraph(playableGraph, buildContext);
            //buildContext = null;
        }

        public void Dispose() {
            if (isValid) {
                rootNode->Dispose();
                //UnsafeUtility.FreeTracked(rootNode, Allocator.Persistent);

                graphContext.Dispose();
                slots.Dispose();
                graphBuffer.Dispose();
                isDisposed = true;
            }
        }
        internal void UpdateBlackboardAddress(ulong inBaseAddress) {
            graphContext.blackboard.SetBaseAddress(inBaseAddress);
        }

        internal GraphContext* GetGraphContext() {
            fixed (GraphContext* ptr = &graphContext) {
                return ptr;
            }
        }

        /// <summary>
        /// 将其它 graph 连接到此 graph 的 slot 上
        /// </summary>
        /// <param name="oldGraphIndex">
        /// 返回被顶替掉的 graph 的序号
        /// 也表示返回状态：
        /// -1：连接成功，且是初次连接，不存在被顶替的graph；
        /// -2：连接成功，但是原先就已经连接了；
        /// -3：连接失败，未查询到此 slot
        /// </param>
        /// <returns>返回连接是否成功</returns>
        internal bool ConnectSlot(AUID slotName, in AnimGraph* graph, out int oldGraphIndex) {
            if (slots.TryGetValue(slotName, out var slot)) {
                oldGraphIndex = slot.Connect(graph);
                if (oldGraphIndex == -2) {
                    // 已经连接在此 slot 了
                    return true;
                }
                slots[slotName] = slot;
                return true;
            }
            oldGraphIndex = -3;
            return false;
        }

        // todo:也可将参数改成indexInArray？
        internal void DisconnectSlot(AUID slotName, in AnimGraph* graph) {
            if (slots.TryGetValue(slotName, out var slot)) {
                slot.Disconnect(graph);
                slots[slotName] = slot;
            }
        }


        // 暂时不需要了，这件事在 Connect 的时候一起做了
        internal void SetGraphContextToSlot(AUID slotName, int graphIndex, in GraphContext* graphContext) {
            if (slots.TryGetValue(slotName, out var slot)) {
                slot.SetGraphContextToSlot(graphIndex, graphContext);
                //slots[slotName] = slot;
            }
        }
        internal IEnumerator<(AUID, int, int)> GetSlotsEnumerator() {
            var iter = slots.GetEnumerator();
            while (iter.MoveNext()) {
                var slot = iter.Current.Value;
                yield return (iter.Current.Key, slot.graphIndexInPort0, slot.graphIndexInPort1);
            }
        }

    }

    // AnimGraph 在 AnimGraphController 的数组中被保存，次结构存储一些管理信息。应当只由 AnimGraphController 来修改
    public struct GraphArrayInfo {
        public int indexInArray; // 自己的序号
        
        public int attachedGraphIndex; // 当前挂载的父级graph的序号
        public AUID slotName; // 当前挂载的父级slot名称
        public GraphArrayInfo(int indexInArray) {
            this.indexInArray = indexInArray;
            attachedGraphIndex = -1;
            slotName = default;
        }
    }

    public struct GraphIdentification {
        public AUID uuid; // 唯一标识
        public AUID name; // 编辑器中可编辑的名字
        public AUID slot; // 所需要挂载的slot（最好能删掉
    }
}
